#####################################################################
# Product:      KnowledgeEngine (Plone Edition)
#
# File:         Model.py
#
# Copyright:    Copyright (c) 2006, Faulkner Technologies
#
# Author:       Brian Oliver
#
# Description:  A Model is a specialised container that is used to
#               define the schema for capturing information during
#               the assessment process.
#
#               Additionally Models provide a basic container mechanism
#               for reporting the outcome of assessments performed
#               against the said Models.
#####################################################################

#####################################################################
## Knowledge Engine Library Imports
from Products.KnowledgeEngine.Assessment import Assessment
from Products.KnowledgeEngine.AssessmentFilter import AssessmentFilter
from Products.KnowledgeEngine.Common import *
from Products.KnowledgeEngine.Core import *
from Products.KnowledgeEngine.EventDefinition import *
from Products.KnowledgeEngine.GenericObject import GenericObject, GenericObjectInfo
from Products.KnowledgeEngine.PermissionDefinition import *
from Products.KnowledgeEngine.PropertyDefinition import *
from Products.KnowledgeEngine.PropertyTypes import *
from Products.KnowledgeEngine.PropertyFormats import *
from Products.KnowledgeEngine.PropertyStructures import *
from Products.KnowledgeEngine.Utils.helpers import renderCSV 
from Products.KnowledgeEngine.Sortable import SortableInfo, Sortable
from Products.KnowledgeEngine.KERFProcessor import *

#####################################################################
## CMF Library Imports
from Products.CMFCore import permissions
from Products.CMFCore.ActionInformation import ActionInformation
from Products.CMFCore.utils import getToolByName

#####################################################################
## Zope Library Imports
from Globals import DTMLFile
from Globals import Persistent
from App.ImageFile import ImageFile
from Products.ZSQLMethods.SQL import SQL
from DateTime import DateTime
from AccessControl import ClassSecurityInfo

#####################################################################
## Python Library Imports
import os
import base64
import binascii

#from Products.KnowledgeEngine.Utils.Profiling import profileit

#####################################################################
## ModelInfo Class Definition
class ModelInfo(GenericObjectInfo, SortableInfo):
    """
        Defines the class info methods, defining information about
        the class without needing an instance of the class
    """


    #---------------------------------------------------------------#

    def getClassName(self):
        """
            Returns the name of the class for which
            we are providing information.
        """
        return 'Model'

    #---------------------------------------------------------------#

    def getDisplayName(self):
        """
            Return a friendly name for the class instances
            (this is used for displaying in lists etc)
        """

        return "Model"

    #---------------------------------------------------------------#

    def getDescription(self):
        """
            ABSTRACT: Return a friendly description on the purpose
            of this class.  This description is often used in the
            Property Editor for instances of this class
        """

        return "Models define the standard schema for assessments."

    #---------------------------------------------------------------#

    def getIdentityType(self):
        """
            Return the identity type.
        """

        return "model"

    #---------------------------------------------------------------#

    def getClassIcon(self):
        """
            Return the appropriate icon file for the class.
        """

        return ImageFile('skins/Model_icon.gif', globals())

    #---------------------------------------------------------------#

    def isContainer(self):
        """
            Returns if the instance is providing folderish/container
            services.

            Override an return true if the instance is to be
            a container for other instances.

            NOTE: all instances support "events" so there is no
            need to return true if you require "event" support.
        """

        return true

    #---------------------------------------------------------------#

    def isPermittedInContainer(self, containerClassInfo, containerInstance = None, instance=None):
        """
            ABSTRACT: Returns if class instances (instance) is permitted in
            the specified container (containerInstance)

            NOTE: This is from this class' instance perspective.
        """

        #Models aren't permitted in any generic object
        return false

    #---------------------------------------------------------------#

    def isPortalObject(self):
        """
            Returns if the object can be placed directly in 
            a Plone Portal (rather than like most objects
            that must exist within a GenericObject container
            object.
            
            eg: Models, EntitySources are Portal Objects.
        """
        
        return true

    #---------------------------------------------------------------#

    def isChildPermitted(self, childClassInfo, childInstance = None):
        """
            Returns if the specified child is permitted as a
            child of the class' instances.

            NOTE: This is from this class' instance perspective.

            For component containers, this always overrides what the
            return value from childClassInfo.isPermittedInContainer(...)
        """


        #Models may only contain Sections and Reports
        return childClassInfo.isInstanceOf('Section') or childClassInfo.isInstanceOf('Report') or childClassInfo.isInstanceOf('Element') or childClassInfo.isInstanceOf('Event')
        
        

    #---------------------------------------------------------------#

    def onConfigureProperties(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific properties using the PropertyReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class properties for reflection
        GenericObjectInfo.onConfigureProperties(self)

        # 
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="editModelLink",
                displayName="Include 'Edit' Link",
                description="Show an edit link in the myknowledge area to this model. NOTE: This is only available for users in the 'Manager' role.",
                defaultValue=False,
                propertyType=pt_BOOLEAN,
                propertyStructure=ps_ATOMIC,
                propertyFormat=pf_NATIVE
            )
        )

        # Add the 'personalRepositoryTitle' property
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="personalRepositoryTitle",
                displayName="Personal Repository Title",
                description="The title used when displaying a users personal repository",
                defaultValue="Your Current Assessments",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                propertyStructure=ps_ATOMIC
            )
        )

        # Add the 'emptyRepositoryMessage' property
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="createAssessmentDescription",
                displayName="Create Assessment Description",
                description="The description of how to create an assessment",
                defaultValue="To create a new assessment, enter the desired name below and press 'create'",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                propertyStructure=ps_ATOMIC
            )
        )

        # Add the 'nonEmptyRepositoryMessage' property
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="emptyRepositoryMessage",
                displayName="Empty Repository Message",
                description="The message to display is a repository does not contain any assessments",
                defaultValue="(You currently don't have any assessments in your personal area for this Model)",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                propertyStructure=ps_ATOMIC
            )
        )

        # Add the 'nonEmptyRepositoryMessage' property
        self.addPropertyDefinition(
            PropertyDefinition(
                identity="nonEmptyRepositoryMessage",
                displayName="Non-Empty Repository Message",
                description="The message to display is a repository does contain any assessments",
                defaultValue="The following assessments are currently in your personal area",
                propertyType=pt_STRING,
                propertyFormat=pf_TEXT_SINGLELINE,
                propertyStructure=ps_ATOMIC
            )
        )

        #add the 'author' property
        self.addPropertyDefinition(PropertyDefinition(identity="author", 
                                                      displayName="Author", 
                                                      description="The author(s) of the Model", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))

        #add the 'permitsConcurrentAssessment' property
        self.addPropertyDefinition(PropertyDefinition(identity="permitsConcurrentAssessment", 
                                                      displayName="Permit Concurrent Assessment?", 
                                                      description="Are multiple users permitted to edit the same assessment simultaneously?", 
                                                      defaultValue=false, 
                                                      propertyType=pt_BOOLEAN, 
                                                      propertyFormat=pf_SELECTION + pf_YES_NO, 
                                                      propertyStructure=ps_ATOMIC))

        #PRIVATE: add the 'lockedForEditingOn' property to determine when a Model was locked to prevent editing.  None means not locked!
        self.addPropertyDefinition(PropertyDefinition(identity="lockedForEditingOn", 
                                                      displayName="Locked On", 
                                                      description="The Zope DateTime the Model was locked", 
                                                      defaultValue=None, 
                                                      propertyType=pt_DATETIME, 
                                                      propertyFormat=pf_NATIVE, 
                                                      propertyStructure=ps_ATOMIC, 
                                                      constructorEVAP=evap_NONE, 
                                                      propertyEditorEVAP=evap_NONE))

        # add the 'searchRelation' property
        # (This field is analogous to an SQL Table name)
        self.addPropertyDefinition(PropertyDefinition(identity="searchRelation", 
                                                      displayName="Search Relation", 
                                                      description="This models relational name (i.e. 'Events')", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))
                                                      
        # add the 'AssessmentTitleTemplate' property
        self.addPropertyDefinition(PropertyDefinition(identity="assessmentTitleTemplate", 
                                                      displayName="Assessment Title Template", 
                                                      description="A KERF or 'script:' statement defining assessment names (Leave blank to allow custom names)", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_MULTILINE, 
                                                      propertyStructure=ps_ATOMIC))     
                                                      
        # add the 'assessmentIdentityTemplate' property
        self.addPropertyDefinition(PropertyDefinition(identity="assessmentIdentityTemplate", 
                                                      displayName="Assessment Identity Template", 
                                                      description="The name of a python script to generate assessment ids. Takes one argument (Model)", 
                                                      defaultValue="", 
                                                      propertyType=pt_STRING, 
                                                      propertyFormat=pf_TEXT_SINGLELINE, 
                                                      propertyStructure=ps_ATOMIC))                                                
                                                      
                                                      
        self.addPropertyDefinition(PropertyDefinition(
            identity       = "enterOnCreate", 
            displayName    = "Enter Assessment on Create",
            description    = "Automatically start an assessment on creation rather than returning to myknowledge area",
            defaultValue   = False,
            propertyType   = pt_BOOLEAN,
            propertyFormat = pf_CHECKBOX
        ))
        
        self.addPropertyDefinition(PropertyDefinition(
            identity       = "autoCreateIndexes", 
            displayName    = "Auto create search indexes in database",
            description    = "Automatically create search indexes in the database",
            defaultValue   = False,
            propertyType   = pt_BOOLEAN,
            propertyFormat = pf_CHECKBOX
        ))                                                                      

        # 20070206 johnm    Added ability to 
        self.addPropertyDefinition(PropertyDefinition(
            identity       = "taggable", 
            displayName    = "Taggable",
            description    = "Allow entry of tags on assessments",
            defaultValue   = False,
            propertyType   = pt_BOOLEAN,
            propertyFormat = pf_CHECKBOX
        ))                                                                      
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="actionMode", 
            displayName="Action Mode", 
            description="Can actions be triggered in batch mode or only one at a time?", 
            propertyType=pt_STRING, 
            propertyFormat=pf_NATIVE, 
            hasEnumeratedValues=true, 
            enumeratedValuesList=["Batch","Single"], 
            defaultValue="Batch",
        ))
                                                                  
        self.addPropertyDefinition(PropertyDefinition(
            identity="noun", 
            displayName="Noun", 
            description="What do you wish to call an assessment? (i.e. Survey)", 
            propertyType=pt_STRING, 
            propertyFormat=pf_TEXT_SINGLELINE, 
        ))
        
        self.addPropertyDefinition(PropertyDefinition(
            identity="noun_plural", 
            displayName="Noun (Plural)", 
            description="What is the plural form of the Noun (i.e. Surveys)", 
            propertyType=pt_STRING, 
            propertyFormat=pf_TEXT_SINGLELINE 
        ))        
                                                                                                                                    
                                                                  
        SortableInfo.onConfigureProperties(self)        



    #---------------------------------------------------------------#

    def onConfigureEvents(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific events using the EventReflection interface.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class events for reflection
        GenericObjectInfo.onConfigureEvents(self)

        #add the onAfterCreateAssessmentInPersonalRepository event (which occurs after an assessment has been created)
        self.addEventDefinition(EventDefinition(identity="onAfterCreateAssessmentInPersonalRepository", displayName="After Creating an Assessment (in user's personal repository)", description="Occurs after an assessment has been created in a user's personal repository", eventType=et_SERVER))

        #add the onBeforeDeleteAssessment event (which occurs prior to an assessment being deleted from the system)
        self.addEventDefinition(EventDefinition(identity="onBeforeDeleteAssessment", displayName="Before Deleting an Assessment", description="Occurs before an assessment is deleted from the knowledge engine", eventType=et_SERVER))

        #add the onBeforeRemoveAssessmentFromPersonalRepository event (which occurs prior to an assessment being removed from a user's personal repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeRemoveAssessmentFromPersonalRepository", displayName="Before Removing an Assessment (from user's personal repository)", description="Occurs before an assessment is removed from a user's personal repository", eventType=et_SERVER))

        #add the onBeforeRemoveAssessmentFromAnyRepository event (which occurs prior to an assessment being removed from any repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeRemoveAssessmentFromAnyRepository", displayName="Before Removing an Assessment (from any repository)", description="Occurs before an assessment is removed from any repository", eventType=et_SERVER))

        #add the onBeforeLockAssessmentInAnyRepository event (which occurs prior to an assessment being locked in any repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeLockAssessmentInAnyRepository", displayName="Before Locking an Assessment (in any repository)", description="Occurs before an assessment is locked in any repository", eventType=et_SERVER))

        #add the onBeforeSaveAssessmentInPersonalRepository event (which occurs prior to an assessment being saved in a user's repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeSaveAssessmentInPersonalRepository", displayName="Before Saving an Assessment (in user's personal repository)", description="Occurs before an assessment is saved in a user's personal repository. (Always occurs before 'for later' or 'submitting' events)", eventType=et_SERVER))

        #add the onBeforeSaveAssessmentInAnyRepository event (which occurs prior to an assessment being saved in any repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeSaveAssessmentInAnyRepository", displayName="Before Saving an Assessment (in any repository)", description="Occurs before an assessment is saved in any repository. (Always occurs before 'for later' or 'submitting' events)", eventType=et_SERVER))

        #add the onBeforeSaveAssessmentForLaterInPersonalRepository event (which occurs prior to an assessment being saved in a user's repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeSaveAssessmentForLaterInPersonalRepository", displayName="Before Saving an Assessment For Later (in user's personal repository)", description="Occurs before an assessment is saved 'for later' in a user's personal repository", eventType=et_SERVER))

        #add the onBeforeSaveAssessmentForLaterInAnyRepository event (which occurs prior to an assessment being saved in any repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeSaveAssessmentForLaterInAnyRepository", displayName="Before Saving an Assessment For Later (in any repository)", description="Occurs before an assessment is saved 'for later' in any repository", eventType=et_SERVER))

        #add the onBeforeSubmitAssessmentFromPersonalRepository event (which occurs prior to an assessment being submitted from a user's repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeSubmitAssessmentFromPersonalRepository", displayName="Before Submitting an Assessment (from user's personal repository)", description="Occurs before an assessment is submitted from a user's personal repository", eventType=et_SERVER))

        #add the onBeforeSubmitAssessmentFromAnyRepository event (which occurs prior to an assessment being submitted from any repository)
        self.addEventDefinition(EventDefinition(identity="onBeforeSubmitAssessmentFromAnyRepository", displayName="Before Submitting an Assessment (from any repository)", description="Occurs before an assessment is submitted from any repository", eventType=et_SERVER))

        #------------------------------------------------------------------------------
        # Add the onAfterSaveAssessmentInPersonalRepository event (which
        # occurs after an assessment is being saved in a user's repository)
        self.addEventDefinition(
            EventDefinition(
                identity="onAfterSaveAssessmentInPersonalRepository",
                displayName="After Saving an Assessment (in user's personal repository)",
                description="Occurs after an assessment is saved in a user's personal repository. (Always occurs after 'for later' or 'submitting' events)",
                eventType=et_SERVER
            )
        )

        # Add the onAfterSaveAssessmentInAnyRepository event (which occurs
        # after an assessment is being saved in any repository)
        self.addEventDefinition(
            EventDefinition(
                identity="onAfterSaveAssessmentInAnyRepository",
                displayName="After Saving an Assessment (in any repository)",
                description="Occurs after an assessment is saved in any repository. (Always occurs after 'for later' or 'submitting' events)",
                eventType=et_SERVER
            )
        )

    #---------------------------------------------------------------#

    def onConfigurePermissions(self):
        """
            Automatically called by the Knowledge Engine once
            the class has been loaded.  Use this method to register
            class specific permission definitions.

            If this class is sub-classed, a call must be made to
            the super-class to ensure super-class properties are
            correctly registered

            NOTE: This method is called before ClassInfo.onAfterStartup()
        """

        #initialise the super-class permissions for reflection
        GenericObjectInfo.onConfigurePermissions(self)

        #add a definition for the 'AccessModel' permission
        self.addPermissionDefinition(PermissionDefinition(identity="AccessModel", displayName="Access Model", description="Permission for a user to access the features of a Model", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'AccessPersonalRepositoryForModel' permission
        self.addPermissionDefinition(PermissionDefinition(identity="AccessPersonalRepository", displayName="Access Personal Repository", description="Permission for a user access their personal assessments and associated reports in a Model", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'CreateAssessmentsInPersonalRepository' permission
        self.addPermissionDefinition(PermissionDefinition(identity="CreateAssessmentsInPersonalRepository", displayName="Create Assessments (in personal repository)", description="Permission to create new assessments in a user's personal repository", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=true, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'EditAssessmentsInPersonalRepository' permission
        self.addPermissionDefinition(PermissionDefinition(identity="EditAssessmentsInPersonalRepository", displayName="Edit Assessments (in personal repository)", description="Permission to open, edit and update assessments in a user's personal repository", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'RemoveAssessmentsInPersonalRepository' permission
        self.addPermissionDefinition(PermissionDefinition(identity="RemoveAssessmentsInPersonalRepository", displayName="Remove Assessments (in personal repository)", description="Permission to remove assessments from a user's personal repository", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'RenameAssessmentsInPersonalRepository' permission
        self.addPermissionDefinition(PermissionDefinition(identity="RenameAssessmentsInPersonalRepository", displayName="Rename Assessments (in personal repository)", description="Permission to rename assessments in a user's personal repository", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'RefundCreditsForAssessmentDeletion' permission
        self.addPermissionDefinition(PermissionDefinition(identity="RefundCreditsForAssessmentDeletion", displayName="Refund Credits When Deleting Assessments", description="Permission to refund credits for assessments when deleted", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'DeleteAnyAssessment' permission
        self.addPermissionDefinition(PermissionDefinition(identity="DeleteAnyAssessment", displayName="Delete Any Assessment (in assessment explorer)", description="Permission to delete and destroy any assessment in the assessment explorer", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'ChangeAnyAssessmentLockState' permission
        self.addPermissionDefinition(PermissionDefinition(identity="ChangeAnyAssessmentLockState", displayName="Change Assessment Lock State (in assessment explorer)", description="Permission to open, edit and update any assessment in the assessment explorer", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'ExploreAllAssessmentsForModel' permission
        self.addPermissionDefinition(PermissionDefinition(identity="ExploreAllAssessments", displayName="Explore All Assessments (in assessment explorer)", description="Permission to list all assessments, including the checked-out, anonymous and orphaned assessments in the assessment explorer", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true, usesAssessmentScope=true))
        
        #add a definition for the 'ListAssessments' permission 
        self.addPermissionDefinition(PermissionDefinition(identity="ListAssessments", displayName="List Assessments (i.e. in Entity Chooser)", description="Permission to list all assessments in this model", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true, usesAssessmentScope=false))        

        #add a definition for the 'EditAnyAssessment' permission
        self.addPermissionDefinition(PermissionDefinition(identity="EditAnyAssessment", displayName="Edit Any Assessment (in assessment explorer)", description="Permission to open, edit and update any assessment, including the checked-out, anonymous and orphaned assessments in the assessment explorer", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'RenameAnyAssessment' permission
        self.addPermissionDefinition(PermissionDefinition(identity="RenameAnyAssessment", displayName="Rename Any Assessment (in assessment explorer)", description="Permission to rename any assessment in the assessment explorer", securableScope=SS_ALL, actorScope=AS_ALL, usesCredits=false, isCommencementPermitted=true, isExpiryPermitted=true))

        #add a definition for the 'RenameAnyAssessment' permission
        self.addPermissionDefinition(
            PermissionDefinition(
                identity="AccessAssessmentTags",
                displayName="Access Tags For Any Assessment",
                description="Permission to access tags of any assessment",
                securableScope=SS_ALL,
                actorScope=AS_ALL,
                usesCredits=false,
                isCommencementPermitted=true,
                isExpiryPermitted=true
            )
        )

    #---------------------------------------------------------------#

#####################################################################
## Model Class Definition
class Model(GenericObject, Sortable):
    """
        Base container for all Faulkner KnowledgeEngine Components
    """

    security = ClassSecurityInfo()

    #---------------------------------------------------------------#

    #ABSTRACT TRANSIENT: a ClassInfo implementation instance to provide info about this class
    _CLASSINFO = ModelInfo()

    #---------------------------------------------------------------#
    # Default Attribute Values
    meta_type = 'Model'
    portal_type = 'Folder'

    #---------------------------------------------------------------#

    def __init__(self, id, identity, createTemplate=True):
        "Constructor for the object"

        GenericObject.__init__(self, id, identity)

        #perform onAfterInstanciation
        self.onAfterInstanciation()

        if createTemplate:

            # Add some default components
            report  = self.createObject( CLASSNAME='SimpleReport', IGNORE_CONSTRUCTOR=True )
    
            section = self.createObject( CLASSNAME='Section', IGNORE_CONSTRUCTOR=True )
            setattr(section, 'title', 'Main')
    
            toolbar = self.createObject( CLASSNAME='Toolbar', IGNORE_CONSTRUCTOR=True )
            button  = toolbar.createObject( CLASSNAME='SubmitButton', IGNORE_CONSTRUCTOR=True )
            setattr(button, 'title', 'Submit')
            setattr(button, 'displayOn', section.getIdentity())

    #---------------------------------------------------------------#

    def onAfterInstanciation(self):
        """
            Handle Knowledge Engine specific actions after this object
            has been loaded into memory.

            This is useful for performing backwards compatibility
        """

        #perform default onAfterInstanciation for the container
        GenericObject.onAfterInstanciation(self)

        #we reindex the model if it hasn't been reindexed since the last version change or the last boot
        #if (not hasattr(self, "version") and not hasattr(self, "lastCatalogTime")) or (self.version != getVersion() or self.lastCatalogTime.lessThan(getBootTime())):                      
        #   self.reindexModel()

        #does the model support concurrent assessment status yet?
        if not hasattr(self, "permitsConcurrentAssessment"):
            self.permitsConcurrentAssessment = false

        #does the model support locking yet?
        if not hasattr(self, "lockedForEditingOn"):
            self.lockedForEditingOn = None

    #---------------------------------------------------------------#

    def getDescription(self):
        "Returns the Description of the model"
        return self.description

    #---------------------------------------------------------------#

    def setDescription(self, description):
        "Sets the Description of the model"
        self.description = description


    # ------------------------------------------------------------------------
    def getEditModelLink(self):
        """
            Return a BOOLEAN on whether a link to edit the model in question
            should be displayed. This only applies to 'Managers'
        """

        return getattr(self, 'editModelLink', False)


    # ------------------------------------------------------------------------
    def setEditModelLink(self, newValue):
        """
            Sets the BOOLEAN about whether a to display link to edit each
            model listed in the portlet
        """

        # Do nothing if the old and new values are identical
        if newValue == self.getEditModelLink():
            return

        self.editModelLink = newValue


    # -------------------------------------------------------------------------
    def getPersonalRepositoryTitle(self):
        """
            Returns the title of the personal repository
        """

        return getattr(self, 'personalRepositoryTitle', 'xYour Current %s' % self.getNoun_plural())


    # -------------------------------------------------------------------------
    def getEmptyRepositoryMessage(self):
        """
            Returns the message that will displayed when a repository is empty
        """
    
        return getattr(self, 'emptyRepositoryMessage', "(You currently don't have any %s in your personal area for this Model)" % self.getNoun_plural())


    # -------------------------------------------------------------------------
    def getNonEmptyRepositoryMessage(self):
        """
            Returns the message that will displayed when a repository is empty
        """
    
        return getattr(self, 'nonEmptyRepositoryMessage', "The following %s are currently in your personal area" % self.getNoun_plural())


    # -------------------------------------------------------------------------
    def getCreateAssessmentDescription(self):
        """
            Returns the description displayed next to the 'create assessment'
            box.
        """
    
        return getattr(self, 'createAssessmentDescription', "To create a new assessment, enter the desired name below and press 'create'")


    #---------------------------------------------------------------#

    def getAuthor(self):
        "Returns the Author of the model"
        return self.author

    #---------------------------------------------------------------#

    def setAuthor(self, author):
        "Sets the Author of the model"
        self.author = author

    #---------------------------------------------------------------#
    

    
    def getPortalObject(self):
        """
            Return the portal level object (like a Model or EntitySource)
            in which this object is located
        """

        return self
    
    #---------------------------------------------------------------#
    
    def loadAssessment(self, assessmentIdentity, userIdentity = None, repositoryIdentity = None, revision=None):
        """ Loads the specified assessment for this model """
        
        storageService = self.knowledgeengine_assessmentstorageservice
        return storageService.loadAssessment( assessmentIdentity, [self], userIdentity, repositoryIdentity, revision)
        
        
    
    #---------------------------------------------------------------#    

    def getPermitsConcurrentAssessment(self):
        """
            Returns if the model supports multiple users
            editing individual assessments simultaneously
            
            RETURN TYPE: Boolean
        """            

        return self.permitsConcurrentAssessment

    #---------------------------------------------------------------#

    def setPermitsConcurrentAssessment(self, permitsConcurrentAssessment):
        """
            Sets if the model supports multiple users
            editing individual assessments simultaneously
        """            

        self.permitsConcurrentAssessment = permitsConcurrentAssessment

    #---------------------------------------------------------------#

    def isLockedForEditing(self):
        """
            Returns if the model is currently locked for editing
        """
        
        return self.lockedForEditingOn is not None

    #---------------------------------------------------------------#

    def getLockedForEditingOn(self):
        """
            Returns the DateTime when the Model was locked for editing
            or None if it is not locked
        """
        
        return self.lockedForEditingOn

    #---------------------------------------------------------------#

    def lockForEditing(self):
        """
            Sets the model to be locked for editing (if not already locked)
        """            

        if not self.isLockedForEditing():
            self.onBeforeLockedForEditing()
            self.lockedForEditingOn = DateTime()

    #---------------------------------------------------------------#

    def unlockForEditing(self):
        """
            Sets that the model is available for editing (not locked)
        """
        
        if self.isLockedForEditing():
            self.lockedForEditingOn = None
            self.onAfterUnlockedForEditing()

    #---------------------------------------------------------------#

    def onBeforeLockedForEditing(self):
        """
            Handle Knowledge Engine specific actions before this Model
            is locked
        """
        
        #nothing to do here (yet)
        pass  

    #---------------------------------------------------------------#

    def onAfterUnlockedForEditing(self):
        """
            Handle Knowledge Engine specific actions after this Model
            is unlocked
        """
        
        #nothing to do here (yet)
        pass   

    #---------------------------------------------------------------#

    def onAfterAdd(self):
        """
            Handle Knowledge Engine specific actions after this Model
            has been added into a container.
        """

        #perform normal onAfterAdd processing
        GenericObject.onAfterAdd(self)

        #notify the model catalog that the model has been added
        try:
            #TODO: register the Model with a catalog
            pass

        except:
            pass

        ###print "DEBUG: Model.onAfterAdd: Completed"

    #---------------------------------------------------------------#

    def onBeforeIdentityChange(self):
        """
            Handle Knowledge Engine specific actions before the
            identity of this object is changed.

            Called By GenericObject.onAfterClone
        """

        #perform normal onBeforeIdentityChange processing
        GenericObject.onBeforeIdentityChange(self)

        try:
            ## The following code prevents duplicate Model cache entries
            ## being created in the model catalog when a Model is
            ## copied.  That is, one model with two different Identities in
            ## the target container.
            ##
            ## The problem is caused by the Zope platform performing a copy,
            ## then an add before the clone callback is called.
            ##
            ## The process is as follows;
            ## 1. copy is created (without changing Identities... no problem)
            ## 2. copy is added into the target container, which is different
            ##    from the container being copied from.  Because the model
            ##    doesn't exist in the new container, the Model is
            ##    added to the cache, with the original identity!
            ## 3. the onAfterClone callback is performed, which allocates
            ##    new identities to the copies.  The model is then added to
            ##    the target container cache (again), but this time with a
            ##    new identity... there you go, one model, two entries in an
            ##    container cache.

            #TODO:
            pass

        except:
            pass

    #---------------------------------------------------------------#

    def onAfterClone(self, identityMappings = None):
        "Handle Knowledge Engine specific actions after this object has been cloned"

        if identityMappings is None: identityMappings = {}

        #perform normal onAfterAdd processing
        GenericObject.onAfterClone(self, identityMappings)

        #notify the model catalog that the model has been cloned (added)
        try:
            #TODO: update the catalog of models
            pass

        except:
            pass

        #unlock the pasted model!
        self.unlockForEditing()

        ###print "DEBUG: Model.onAfterClone: Completed"

    #---------------------------------------------------------------#

    def onBeforeDelete(self):
        """
            Handle Knowledge Engine specific actions before this Model is deleted
        """

        #-----------------#
        #The following code has been commented out to preserve assessment data when a model is deleted.
        ###remove component assessment data for this model
        ##for classInfo in getRegisteredClassInfo():
        ##    #is the class assessible?
        ##    if classInfo.isInstanceOf('Assessible'):
        ##        classInfo.removeState(self)
        ##
        ###remove the assessments for the model
        ##sql = "DELETE FROM assessments WHERE modelid='%s'" % (self.getIdentity(),)
        ##self.getSQLQuery()(query=sql)
        #-----------------#

        #perform normal onBeforeDelete processing
        GenericObject.onBeforeDelete(self)

        #notify the model catalog that the Model is about to be deleted
        try:
            #TODO: update the catalog of the deletion
            pass

        except:
            pass

        ###print "DEBUG: Model.onBeforeDelete: Completed"

    #---------------------------------------------------------------#

    def reindexModelAssessments(self):
        """
            Reindexes this models assessments
        """
        
        storageService = self.knowledgeengine_assessmentstorageservice
        
        return storageService.reindexAssessments( self.getIdentity() )

    #---------------------------------------------------------------#

    def reindexModel(self):
        """
            Attempt to reindex the entire model into the generic
            object catalog service. This is slow but should hopefully
            not be done too often.
        """

        #remember the version of the ke and time we reindexed this model
        #(so we only reindex on reboot if we really need too)
        self.version = getVersion()
        self.lastCatalogTime = DateTime()

        descendants = self.getDescendants()

        for descendant in descendants.values():
            descendant.indexObject()

        #reindex the model itself
        self.indexObject()

        return "Done"

    #---------------------------------------------------------------#

    def repermissionModel(self):
        """
            Attempt to repermission the entire model and it's
            components. This is primarily used as a migration tool
            from KE v3.x
        """

        def _permissionObject(obj):
            if obj.isContainer():
                for child in obj.getChildren():
                    _permissionObject(child)

            obj.manage_role(
                role_to_manage='Authenticated',
                permissions=[
                    'Access contents information',
                    'View',
                ]
            )


        _permissionObject(self)
        
        return "%s repermissioned" % self.getTitleOrId()

    #---------------------------------------------------------------#

    def resetIsMandatoryComponents(self):
        """
            Run through all of the components of this model turning
            the isMandatory field off
        """

        def _resetIsMandatory(obj):
            if obj.isContainer():
                for child in obj.getChildren():
                    _resetIsMandatory(child)

            if hasattr(obj, 'setIsMandatory'):
                obj.setIsMandatory(False)

        _resetIsMandatory(self)

        return "%s's components set to optional" % self.getTitleOrId()

    #---------------------------------------------------------------#

    def onAfterAddComponent(self, component):
        """
            Handle when a component has beed added somewhere in the Model.
            Called by Component.onAfterAdd.
        """

        ###print "DEBUG: Model.onAfterAddComponent: Commenced"

        ###print "DEBUG: Model.onAfterAddComponent: Completed"

    #---------------------------------------------------------------#

    def onBeforeDeleteComponent(self, component):
        """
            Handle when a component is about to be deleted from
            somewhere with in the Model.
            Called by Component.onBeforeDelete.
        """

        ###print "DEBUG: Model.onBeforeDeleteComponent: Commenced"

        ###print "DEBUG: Model.onBeforeDeleteComponent: Completed"

    #---------------------------------------------------------------#

    def containsComponent(self, identity):
        """
            Returns if the component with the specified identity
            is in the model component dictionary
        """

        #get the component
        component = self.getComponent(identity)

        #does it exist?
        return not component is None

    #---------------------------------------------------------------#

    def getGenericObjectCatalogService(self):
        """ get the generic object catalog service
        """

        return getToolByName(self, 'knowledgeengine_genericobjectcatalogservice', None)

    #---------------------------------------------------------------#

    def getComponent(self, identity, forceLookup = false):
        """ DEPRECATED: use getObject() instead
        """

        ###print "DEBUG: Model.getComponent(%s): Commenced " % identity

        return self.getGenericObject(identity, forceLookup = false)

        ###print "DEBUG: Model.getComponent(%s): Completed " % identity


    #---------------------------------------------------------------#

        

    #---------------------------------------------------------------#

    def getGenericObject(self, identity, forceLookup = false):
        """
            Return the component with the specified identity
            that exists within the Model. Actually passes all the work
            onto the catalog.
        """

        #TODO - check that identity is valid prior to lookup

        #forced reindex
        if forceLookup:
            self.reindexModel()

        objectHandles = self.getGenericObjectHandles(getIdentity=identity)

        if len(objectHandles) == 1:
            return objectHandles[0].getObject()

        if len(objectHandles) > 1:
            raise "Got multiple results looking for component: " + `identity`

        return None

    #---------------------------------------------------------------#

    def getGenericObjectHandles(self, REQUEST=None, **kw):
        """
            Proxy to the catalog service.

            Return ZCatalog Brains handles to the generic objects matching the search criteria

            You can call this by passing the request or specific keywords
        """

        catalog = self.getGenericObjectCatalogService()

        if REQUEST:
            query = REQUEST
        else:
            query = kw
        
        return catalog.searchResults(query) 

    #---------------------------------------------------------------#

    def getSections(self):
        """
            Returns a sequence of the immediate child Sections(s)
            in this container, sorted by the Component.getOrder()
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the Section(s) only
        sections = [x for x in children if x.isInstanceOf("Component") and x.isContainer()]

        #sort the component by Component.getOrder()
        sections.sort(lambda x, y : -(x.getOrder() < y.getOrder()))

        #ensure that the sections have unique positions
        if self.isOrderedContainer():
            lastChild = None
            for child in sections:
                if lastChild == None:
                    lastChild = child
                else:
                    isAscending = lastChild.getOrder() < child.getOrder()

                    if not isAscending:
                        child.setOrder(lastChild.getOrder() + 1)

                    lastChild = child

        return sections

    #---------------------------------------------------------------#

    def getPages(self, assessment = None):
        """
            Returns a sequence of child Components (that may or not
            be ComponentContainers) that are considered top-level
            pages.  Takes into account the 'promotion' of children
            to be top-level pages, including nested promotions.

            If an assessment is not provided, all possible
            pages must be returned.
        """

        #get all of the sections
        componentStack = self.getSections()

        #the resulting pages
        pages = []

        #go through the componentStack, and promote the appropriate children
        while len(componentStack) > 0:

            #pop the first component
            component = componentStack[0]
            del componentStack[0]

            

            #is the component a section?
            if component.isInstanceOf("Section"):

                #is the section promoting it's children
                if component.getIsPromotingChildren():
                    #push the promoted children onto the front of the components stack
                    componentStack = component.getPromotedChildren(assessment) + componentStack
                else:
                    #a normal section... so it's a page
                    pages = pages + [component]

            else:
                #not a section... so add the component as a page
                pages = pages + [component]

        return pages

    #---------------------------------------------------------------#

    def getComponentContainers(self):
        """
            Returns a sequence of immediate component containers (like sections)
            in the model, sorted by the Component.getOrder()
        """

        return self.getSections()

    #---------------------------------------------------------------#

    def getReportsByRepository(self, user=None, className="Report"):
        """ Returns all reports in this model, collating by repository """
        
        reports = self.getDescendantsByInstanceOf(className)
        
        collated = {}
        
        securityservice = self.knowledgeengine_securityservice
        
        for report in reports:
            
            if user: # User is specified, so filter.
                permission = securityservice.getEffectivePermission("ViewReport", report.getIdentity(), str(user))
                if not ( permission and permission.granted() ):
                    continue # No access, so skip this report
            
            container = report.aq_parent
            collated.setdefault( container.title_or_id(), [] ).append( report )
            
        return collated
        

    #---------------------------------------------------------------#

    def getReports(self, user = None, className = None):
        """
            Returns a sequence of the immediate child Report(s)
            in this container, sorted by title. If className is specified
            then the results are filtered.

            When a user is specified, 'view report' permissions are
            checked for each report - only reports with granted
            view permissions are returned.

            NOTE: does not return reports in other components (ie: repositories)
        """

        #get the immediate children of the container
        children = self.objectValues()

        #print `children`

        #filter the Report(s) only
        reports = [x for x in children if x.isInstanceOf("Report")]

        #filter the specific classes of reports
        if className != None:
            reports = [x for x in reports if x.isInstanceOf(className)]

        #filter based on user permissions (if a user was provided)
        if user is not None:
            #grab the security service so we can check some permissions
            securityService = getSecurityService(self)

            #grab the credit management service so we can check credits
            creditManagementService = getCreditManagementService(self)

            #determine the permitted reports
            permittedReports = []
            for report in reports:

                #get the permission for the current report
                permission = securityService.getEffectivePermission("ViewReport", report.getIdentity(), user.getIdentity())

                #is the current report permitted?
                if permission is not None and permission.granted() and creditManagementService.getEffectiveCredits(permission, report, user) != 0:
                    permittedReports = permittedReports + [report]

            #set the resulting permitted reports
            reports = permittedReports

        #sort the report by Report.getTitle()
        reports.sort(lambda x, y : x.getTitle() < y.getTitle())

        return reports

    #---------------------------------------------------------------#

    def getRepositories(self):
        """
            Returns a sequence of the assessment repositories for
            this model.

            NOTE: does not include the implicit User or Model
            repositories.
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the Repositories(s) only
        repositories = [x for x in children if x.isInstanceOf("Repository")]

        #sort the report by Repository.getTitle()
        repositories.sort(lambda x, y : x.getTitle() < y.getTitle())

        return repositories

    #---------------------------------------------------------------#

    def getAssessmentActions(self, user = None):
        """
            Returns a sequence of the assessment actions for
            this model.

            When a user is specified, 'ExecuteAssessmentAction' permissions are
            checked for each assessment action - only actions with granted
            execute permissions for the user are returned.

            NOTE: does not include assessment actions in repositories
        """

        #get the immediate children of the container
        children = self.objectValues()

        #filter the AssessmentActions(s) only
        assessmentActions = [x for x in children if x.isInstanceOf("AssessmentAction")]

        #filter based on user permissions (if a user was provided)
        if user is not None:
            #grab the security service so we can check some permissions
            securityService = getSecurityService(self)

            #determine the permitted actions
            permittedAssessmentActions = []
            for assessmentAction in assessmentActions:
                
                #get the permission for the current report
                permission = securityService.getEffectivePermission("ExecuteAssessmentAction", assessmentAction.getIdentity(), user.getIdentity())

                #is the current action permitted?
                if permission is not None and permission.granted():
                    permittedAssessmentActions = permittedAssessmentActions + [assessmentAction]

            #set the resulting permitted reports
            assessmentActions = permittedAssessmentActions

        #sort the report by AssessmentAction.getTitle()
        assessmentActions.sort(lambda x, y : x.getTitle() < y.getTitle())

        return assessmentActions

    #---------------------------------------------------------------#

    def hasDefaultView(self):
        """
            PLONE Support:

            Returns if the object has a default view.  By setting this
            to true, a view tab will be added to the object.

            If this is false, attempting to visit the / (default view)
            for a component will redirect the client to the
            next available parent that has a default view.

            (Worst case, this is the Model component)
        """

        return false
            
    security.declareProtected('Modify portal content', 'view')            
    def view(self):
        """ Returns the default view for the Model """
                    
        return self.folder_contents(REQUEST=self.REQUEST)

    #---------------------------------------------------------------#

    def isOrderedContainer(self):
        """
            Return if the children of the object may use ordering.

            This is to allow the 'Order' column in the Content
            management tab in Zope
        """

        return true

    #---------------------------------------------------------------#

    def getAbsolutePath(self):
        """
            Return the path relative to the portal
        """

        #get that absolute path of the parent
        return self.absolute_url()

    #---------------------------------------------------------------#

    def getAssessmentStorageService(self):
        """
            Returns the assessment storage service that is used
            to manage assessments for this model
        """

        #we use the Core to find the assessment storage service
        return getAssessmentStorageService(self)

    #---------------------------------------------------------------#

    def getUserProviderService(self):
        """
            Returns the user provider service that is used
            to manage users for this model
        """

        #we use the Core to find the user provider service
        return getUserProviderService(self)

    #---------------------------------------------------------------#

    def getGroupProviderService(self):
        """
            Returns the group provider service that is used
            to manage groups for this model
        """

        #we use the Core to find the group providerservice
        return getGroupProviderService(self)

    #---------------------------------------------------------------#

    def getFirstShownComponent(self, componentList, assessment):
        """
            Returns the first shown component from the specified
            componentList using the provided assessment.
            
            Returns None if all of the components are not shown.
        """
        
        #search for the first component
        for component in componentList:
        
            #is the current component shown?
            if component.isShown(assessment):
                return component
        
        #none shown!
        return None

    #---------------------------------------------------------------#

    def getOrderedAssessibles(self, obj = None):
        """
            Returns an ordered list of the Assessible components
            contained within this model. This was created for use
            with orderly storage of model component values of an
            assessment.
            
            RETURN: [...]

            20070110 johnm  Created
        """

        # If no object is provided, assume we're working with the
        # current model
        if obj is None:
            obj = self

        # Resurse through the model components looking for all
        # Assessibles.
        result = []
        if obj.containsChildren():
            for child in obj.getChildrenByOrder():
                if child.isContainer():
                    result.extend(self.getOrderedAssessibles(child))

                if child.isInstanceOf("Assessible"):
                    result.append(child)

        return result

    #---------------------------------------------------------------#

    def getOrderedAssessibleIdentities(self, obj=None):
        """
            Returns an ordered list of Assessible component identities.
        """
        
        return [component.getIdentity() for component in self.getOrderedAssessibles(obj)]

    #---------------------------------------------------------------#

    def getOrderedExportables(self, obj = None):
        """
            Returns an ordered list of Exportable components contained
            within the model.
        """

        # If no object is provided, assume we're working with the
        # current model
        if obj is None:
            obj = self

        
        # Resurse through the model components looking for all
        # Exportables.
        result = []
        if obj.containsChildren():
            for child in obj.getChildrenByOrder():
                if child.isContainer():
                    result.extend(self.getOrderedExportables(child))

                if child.isInstanceOf("Exportable"):
                    result.append(child)

        return result

    #---------------------------------------------------------------#

    def getOrderedExportableIdentities(self, obj=None):
        """
            Returns an ordered list of Exportable component identities
            with the model.
        """

        return [component.getIdentity() for component in self.getOrderedExportables()]

    #---------------------------------------------------------------#

    def getComponentsBySearchIndices(self, searchIndices=[]):
        """
            Returns a list of ordered components based on the given
            searchIndices.
        """

        return filter(lambda c: c.getSearchIndex() in searchIndices, self.getOrderedAssessibles())
        
#        components = []
#        for component in self.getOrderedAssessibles():
#            if component.getSearchIndex() in searchIndices:
#                components.append(component)
#        
#        return components

    #---------------------------------------------------------------#

    def toCSV(self, assessments=[], componentIdentities=[], searchIndices=[], displayReportContent=False, renderHeader=None):
        """
            Exports the given assessment data as CSV for the fields
            list provided. If neither assessments or fields are given
            *all* is given.
        """

        storageService = getAssessmentStorageService(self)

        # If fields (i.e. component identities) have not been given
        # we need to grab all of the Exportable component identities
        if not componentIdentities:
            if not searchIndices:
                components = self.getOrderedExportables()
            else:
                components = self.getComponentsBySearchIndices(searchIndices=searchIndices)
        else:
            components = [self.getGenericObject(identity) for identity in componentIdentities]

        # If we haven't been given any assessments, grab all of them
        if not assessments:
            assessments = storageService.getAssessmentsForModel(self)

        # Setup the appropriate fields
        fields = ['Created By', 'Instant Created', 'Instant Modified']

        # Iterate through all of the assessments grabbing the CSV
        # friendly value for inclusion in the resulting CSV export
        csvResults = []
        for assessment in assessments:
            csvValues = assessment.getComponentCSVValues(displayReportContent=displayReportContent)
            row = {}
            for component in components:
                key = "%s (%s %s)" % (component.getTitleOrId(), component.getIdentity(), component.getSearchIndex())
                if key not in fields:
                    fields.append(key)
                row[key]    = csvValues[component.getIdentity()]
            
            # 20070119 johnm    Added creation and modification dates
            #                   as well as created by fields
            row['Created By']       = assessment.getCreatedBy()
            row['Instant Created']  = assessment.getInstantCreated()
            row['Instant Modified'] = assessment.getInstantCreated()

            csvResults.append(row)

        # Render it and return                     
        return renderCSV(fields, csvResults, renderHeader=renderHeader)

    #---------------------------------------------------------------#

    #@profileit(200)
    def assess(self, **args):
        """
            Handles the GET and POST requests when performing an
            assessment.

            Provides the mechanics of page navigation, redirection and
            event handling.

            Actual rendering of the assessment editor is performed
            in the Model_assessmenteditor.pt
        """

        REQUEST = self.REQUEST

        #print "ASSESS: %s, %s" % ( `REQUEST.form`, `args` )

        #get the user id of the currently authenticated user
        zopeUserIdentity = REQUEST["AUTHENTICATED_USER"].getId()
        userIdentity = iif(zopeUserIdentity is None, "anonymous", zopeUserIdentity)

        #get the User from the UserProviderService (we may even store it in the session!)
        userProviderService = getUserProviderService(self)
        user = userProviderService.getUser(userIdentity)

        #get the security service... so we can check security
        securityService = getSecurityService(self)

        #get the assessment storage service... we'll need that to manage the assessment
        storageService = getAssessmentStorageService(self)

        #let's use a sensible case for the REQUEST
        request = REQUEST

        #get the session from the request
        session = request["SESSION"]

        #get the response from the request (weird I know, but that's where it lives in Zope)
        response = request["RESPONSE"]

        ###determine the destination URL (where to go if we are leaving the assessment editor)       
        #(exitURLs have priority over others)
        portal = self.portal_url.getPortalObject()

        #POST request will have an appropriate location        
        if request["REQUEST_METHOD"] == "POST":        

            #has a 'gotolocation' been provided?
            if request.get("gotolocation", None):
                destinationURL = request.get("gotolocation")
                exitURL = ""

            #has an exitURL been specified?
            elif request.get("exitURL", None):
                destinationURL = request.get('exitURL')
                exitURL = destinationURL

            # We'll default to getting back to the myk area if we haven't
            # found one already
            else:
                destinationURL = '%s/myknowledge' % (portal.absolute_url())
                exitURL = ""


        #GET request may have an exit URL in the query string            
        elif request.has_key("exitURL"):
            #grab the entire GET query string and chop of the bit before the exitURL
            #as it contains the standard part of the URL
            exitURL = request["QUERY_STRING"]
            exitURL = exitURL[exitURL.find("exitURL") + 8:]
            destinationURL = exitURL

        #no exit URL, so assume we are going back to the myknowledge area
        else:
            destinationURL = '%s/myknowledge' % (portal.absolute_url())
            exitURL = ""
        
        
        ###determine the repository from which the assessment is being opened
        if request["REQUEST_METHOD"] == "POST" and request.form.has_key('openedFrom'):
            openedFromRepositoryIdentity = request.form['openedFrom']
        elif request.has_key("openedFrom"):
            openedFromRepositoryIdentity = request["openedFrom"]
        else:
            openedFromRepositoryIdentity = None
            
        if openedFromRepositoryIdentity == '':
            openedFromRepositoryIdentity = None

        ###determine the assessmentIdentity we are to use for this assessment
        #first try the request
        if request.has_key("assessmentIdentity"):
            assessmentIdentity = request["assessmentIdentity"]

        #next try the session
        elif session.has_key("assessmentIdentity"):
            assessmentIdentity = session["assessmentIdentity"]

        #ok... no assessment exists... we might be allowed to create a new one
        else:
            assessmentIdentity = None

        #determine the last revision number for the assessment
        revision = request.get('ASSESSMENT_REVISION', None)


        ###attempt to load the assessment (if we've got an identity to load and we have the permission to load it)
        #(assume we can't find an assessment)
        assessment = None
        
        if assessmentIdentity is not None:
            #we assume the user doesn't have permission (to be on the safe side)
            mayEditAssessment = false
        
            #get the users permission to 'EditAnyAssessment'
            editAnyAssessmentPermission = securityService.getEffectivePermission("EditAnyAssessment", self.getIdentity(), user.getIdentity())
        
            #is the user denied editing any assessment?
            if editAnyAssessmentPermission is not None and editAnyAssessmentPermission.denied():
                #the user has been explicitly denied permission to edit assessments in the model
                mayEditAssessment = false
                
            #is the assessment in the users repository                
            else:
                #load the assessment (so we can find out where it lives)
                assessment = storageService.loadAssessment(assessmentIdentity, self, user.getIdentity(), openedFromRepositoryIdentity, revision)

                #does the assessment exist?
                if assessment is not None:
                    #can user edit any assessment? (this overrides local edit definitions)
                    if editAnyAssessmentPermission is not None and editAnyAssessmentPermission.granted():
                        #the user has been explicitly permission to edit any assessments in the model
                        mayEditAssessment = true
                    
                    else:
                        #is the assessment in the users personal repository and does the user have permission to edit it
                        if storageService.isAssessmentInRepository(assessment, user.getIdentity()):
                            editAssessmentsInPersonalRepositoryPermission = securityService.getEffectivePermission("EditAssessmentsInPersonalRepository", self.getIdentity(), user.getIdentity())

                            if editAssessmentsInPersonalRepositoryPermission is not None and editAssessmentsInPersonalRepositoryPermission.denied():
                                #the user has been explicitly denied permission to edit their assessments in the model
                                mayEditAssessment = false

                            elif editAssessmentsInPersonalRepositoryPermission is None:
                                #the user doesn't have permission defined for their own assessment or 'any' other assessments in the model
                                mayEditAssessment = false

                            else:
                                #the user has permission to edit
                                mayEditAssessment = true

                        #check if the assessment is in a repository and that the user has the permission to edit it
                        for repository in self.getRepositories():

                            #is the assessment in the repository and does the user have permission to edit it
                            if storageService.isAssessmentInRepository(assessment, repository.getIdentity()):
                                editRepositoryAssessmentsPermission = securityService.getEffectivePermission("EditRepositoryAssessments", repository.getIdentity(), user.getIdentity())

                                if editRepositoryAssessmentsPermission is not None and editRepositoryAssessmentsPermission.denied():
                                    #the user has been explicitly denied permission to edit the assessments in the repository
                                    mayEditAssessment = mayEditAssessment or false

                                elif editRepositoryAssessmentsPermission is None:
                                    #the user doesn't have permission defined for assessments in the repository or 'any' other assessments in the model
                                    mayEditAssessment = mayEditAssessment or false

                                else:
                                    #the user has permission to edit
                                    mayEditAssessment = mayEditAssessment or true

                    #can we proceed to edit the assessment?
                    if not mayEditAssessment:
                        #the user doesn't have permission to edit the assessment
                        #(let's so we will redirect them back to /myknowledge area)
                        failureMessage = "Sorry.+You+don't+have+permission+to+edit+the+specified+assessment+for+%s" % (self.getTitleOrId(),)
                        destinationURL = destinationURL + iif(destinationURL.find('?') < 0, '?', '&') + 'portal_status_message=' + failureMessage
                        return response.redirect(destinationURL)

        else:
            #no identity means no assessment!
            assessment = None

        #if we don't have an assessment, redirect the user back to /myknowledge
        if assessment is None:
            #we must have a specified assessment to assess!
            failureMessage = "Sorry.+The+assessment+you+attempted+to+access+does+not+exist+for+%s" % (self.getTitleOrId(),)
            destinationURL = destinationURL + iif(destinationURL.find('?') < 0, '?', '&') + 'portal_status_message=' + failureMessage
            return response.redirect(destinationURL)

        #let's checkout the assessment
        assessment = storageService.checkoutAssessment(assessment, user)
        
        #did we sucessfully checkout the assessment?
        if not self.getPermitsConcurrentAssessment() and assessment.getCheckedOutBy() != user.getIdentity():
            #we failed to checkout the assessment
            failureMessage = "Sorry.+The+assessment+you+attempted+to+edit+is+in+use+by+another+user.+Please+try+again+later."
            destinationURL = destinationURL + iif(destinationURL.find('?') < 0, '?', '&') + 'portal_status_message=' + failureMessage
            return response.redirect(destinationURL)

        #determine the type of request that has been made.
        #for 'GET' requests, we want to render the assessment editor.
        #for 'POST' requests, we want to store the returned assessment component states and do the appropriate action.
        if request["REQUEST_METHOD"] == "POST":

            errors = {}
            #process the POST request

            #get the page from which the POST originated
            pageIdentity = request["pageIdentity"]
            page = self.getComponent(pageIdentity)

            # We want to use the current page (section component) as the
            # starting point for so that we don't inadvertantly check
            # conditions on components contained elsewhere within the
            # model.
            orderedAssessibles = self.getOrderedAssessibleIdentities(obj=page)

            # Just grab the form values that are in fact assessible components
            componentDict = {}
            for (componentIdentity, value) in request.form.items():
                if componentIdentity in orderedAssessibles:
                    componentDict[componentIdentity] = value

            #update the assessment with the component state in the request 
            #(each component is responsible for this)
            for componentIdentity in orderedAssessibles:
                component = self.getGenericObject(componentIdentity)
                if componentIdentity in componentDict: 
                    value = componentDict[componentIdentity]
                    try:
                        component.updateAssessmentState(assessment, value)
                    except ValidationException, e:
                        msg       = e.getValue()
                        component = e.getComponent()
                        errors[component.getIdentity()] = msg

                # Need to check if the component is mandatory as there are
                # occasions where the form inputs are dynamically generated
                # (see EntityChooser) and might not exist in the REQUEST
                elif component.getIsMandatory():
                    
                    if component.isEditable( assessment ) and component.isShown( assessment ):
                    
                        # We don't want to check on nagigation or when entering
                        # a wizard.
                        if not (request["gotoaction"].startswith("assessmentWizard:") or request["gotoaction"] == "navigate"):
                            errors[componentIdentity] = "Field Required." 
            
            if errors:
                request['gotolocation'] = request['pageIdentity']
                request['gotoaction']   = 'navigate'
                request['portal_status_message'] = "Please correct the indicated errors"
                request['ERRORS'] = errors

            if not page:
                raise Exception, "Could not find component %s. Try Reindexing" % `pageIdentity`

            #handle the 'onBeforeLeaving' event for the page
            if page.isEventEnabled("onBeforeLeaving"):
                page.getEnabledEvent("onBeforeLeaving").execute(assessment, user)

            #-----------------------------------
            
            #navigate to another page template (build a query string and redirect to it)
            if request['gotoaction'] == "navigateTemplate":

                #save the assessment using the assessment storage service
                storageService.saveAssessment(assessment)
                
                url = request['gotolocation']
                if not '?' in url: url += "?"
                
                fields = "assessmentIdentity","ASSESSMENT_REVISION","exitURL","openedFrom"
                
                # Build the query string
                for key in fields:
                    try:
                        value = request[key]
                    except KeyError:
                        continue
                    
                    url += "&%s=%s" % ( key, value )
                    
                return request.RESPONSE.redirect( url )
                
            #do we need to open a wizard during the process of the assessment?
            #(upon exiting the wizard, control will be returned to the assessment editor)
            if request["gotoaction"].startswith("assessmentWizard:"):
            
                #save the assessment using the assessment storage service
                #(we'll return to it after the wizard has been processed)
                storageService.saveAssessment(assessment)
                
                #get the dialog name and the identity of component that will provide the wizard definition
                wizardName = request["gotoaction"][request["gotoaction"].find(":")+1:]
                componentId = request["gotolocation"]
                component = self.getComponent(componentId)
                
                #get the wizard definition and state from the component
                (wizard, wizardState) = component.getAssessmentWizardAndWizardState(assessment, wizardName, user=user)

                #do we have a wizard?
                if wizard is not None:
                    #inject the current assessment and state into the wizard state 
                    #so we can later recover where we are in the assessment
                    wizardState.setPropertyValue('wizardName', wizardName)
                    wizardState.setPropertyValue('componentIdentity', component.getIdentity())
                    wizardState.setPropertyValue('modelIdentity', self.getIdentity())
                    wizardState.setPropertyValue('assessmentIdentity', assessment.getIdentity())
                    wizardState.setPropertyValue('assessmentRevision', revision)
                    wizardState.setPropertyValue('pageIdentity', page.getIdentity())
                    wizardState.setPropertyValue('openedFrom', openedFromRepositoryIdentity)
                    wizardState.setPropertyValue('exitURL', exitURL)
                    
                    #put the wizardState into the session (so we can get it back later)
                    session['wizardState'] = wizardState
                    
                    #call the component Component_assessmentwizard_form kick-off the wizard
                    return component.Component_assessmentwizard_form(REQUEST=request, EDITINGOBJECT=self, WIZARD=wizard, WIZARDSTATE=wizardState)
 
                else:
                    #force the request to seem like a navigation back to the same page
                    request["gotoaction"] = "navigate"
                    request["gotolocation"] = page.getIdentity()
            
            #are we to navigate to another page?
            if request["gotoaction"] == "navigate":
            
                #save the assessment using the assessment storage service
                storageService.saveAssessment(assessment)

                
                #get the destination page
                pageIdentity = request["gotolocation"]
                page = self.getComponent(pageIdentity)

                #set the destination page in the request
                request["PAGE"] = page

                #handle the 'onBeforeVisiting' event for the page
                if page.isEventEnabled("onBeforeVisiting"):
                    page.getEnabledEvent("onBeforeVisiting").execute(assessment, user)

                #add the assessment to the request (so we can pass it around when rendering the assessment)
                request["ASSESSMENT"] = assessment

                #
                # 20060712 johnm    Forget to add to assessment navigation (Doh!)
                #
                request["ASSESSMENT_OPENEDFROM"] = assessment.getOpenedFromRepositoryIdentity()

                #request["assessmentIdentity"] = assessment.getIdentity()
                #initialise the request state variables (used to help render a page)
                request["REFRESH_JAVASCRIPT"] = []
                request["DECLARATIONS_JAVASCRIPT"] = {}     #a dictionary (set) to prevent individual component types duplicating declarations
                request["ONLOAD_JAVASCRIPT"] = {}           #a dictionary (set) to prevent individual component types duplicating onload statements
                request["ISPREVIEW"] = false
                #request["ELEMENTS"] = page.getAcquiredElementsDictionary().values()
                #request["ELEMENTS_BY_TYPE"] = page.getAcquiredElementsByTypeDictionary().values()
                request["EXIT_URL"] = exitURL

                #now forward the request to the assessment editor to render the destination page for the assessment
                return self.Model_assessmenteditor(REQUEST=request, **args)

            else:
                #handle saving or submitting the assessment
                
                #determine if the assessment is in the user's personal repository
                isAssessmentInPersonalRepository = storageService.isAssessmentInRepository(assessment, user.getIdentity())

                #determine the repositories where the assessment is currently located
                repositoryIdentitiesInWhichAssessmentIsLocated = storageService.getRepositoriesForAssessment(assessment)

                #handle the 'onBeforeSaveAssessmentInPersonalRepository' event for the user repository (if it's in the user repository)
                if isAssessmentInPersonalRepository and self.isEventEnabled("onBeforeSaveAssessmentInPersonalRepository"):
                    self.getEnabledEvent("onBeforeSaveAssessmentInPersonalRepository").execute(assessment, user)    

                #handle the 'onBeforeSaveAssessmentInRepository' event for the repositories in which the assessment is located
                for repositoryIdentity in repositoryIdentitiesInWhichAssessmentIsLocated:
                    #(we don't include the user repository)
                    if repositoryIdentity != user.getIdentity():
                        repository = self.getComponent(repositoryIdentity)
                        if repository is not None and repository.isEventEnabled("onBeforeSaveAssessmentInRepository"):
                            repository.getEnabledEvent("onBeforeSaveAssessmentInRepository").execute(assessment, user)                        

                #handle the 'onBeforeSaveAssessmentInAnyRepository' event for the model
                if self.isEventEnabled("onBeforeSaveAssessmentInAnyRepository"):
                    self.getEnabledEvent("onBeforeSaveAssessmentInAnyRepository").execute(assessment, user)
                
                
                #handle submitting assessment (if necessary)
                if request["gotoaction"] == "submit":
                    #handle the 'onBeforeSubmitAssessmentFromPersonalRepository' event for the model assessment (if it's in the user repository)
                    if isAssessmentInPersonalRepository and self.isEventEnabled("onBeforeSubmitAssessmentFromPersonalRepository"):
                        self.getEnabledEvent("onBeforeSubmitAssessmentFromPersonalRepository").execute(assessment, user)    
                                        
                    #handle the 'on_before_submit_assessment_from_repository' event for the repository from which the assessment was opened
                    if assessment.getOpenedFromRepositoryIdentity() is not None:
                        #get the repository from the model
                        repository = self.getComponent(assessment.getOpenedFromRepositoryIdentity())
                        
                        #handle the event for the repository if we have one (both repository and event)
                        if repository is not None and repository.isEventEnabled("onBeforeSubmitAssessmentFromRepository"):
                            repository.getEnabledEvent("onBeforeSubmitAssessmentFromRepository").execute(assessment, user)

                    #handle the 'onBeforeSubmitAssessmentInRepository' event for the repositories in which the assessment is located
                    for repositoryIdentity in repositoryIdentitiesInWhichAssessmentIsLocated:
                        #(we don't include the user repository)
                        if repositoryIdentity != user.getIdentity():
                            repository = self.getComponent(repositoryIdentity)
                            if repository is not None and repository.isEventEnabled("onBeforeSubmitAssessmentInRepository"):
                                repository.getEnabledEvent("onBeforeSubmitAssessmentInRepository").execute(assessment, user)                        

                    #handle the 'onBeforeSubmitAssessmentFromAnyRepository' event for the model
                    if self.isEventEnabled("onBeforeSubmitAssessmentFromAnyRepository"):
                        self.getEnabledEvent("onBeforeSubmitAssessmentFromAnyRepository").execute(assessment, user)

                #handle saving for later
                else:
                    #handle the 'onBeforeSaveAssessmentForLaterInPersonalRepository' event for the user repository (if it's in the user repository)
                    if isAssessmentInPersonalRepository and self.isEventEnabled("onBeforeSaveAssessmentForLaterInPersonalRepository"):
                        self.getEnabledEvent("onBeforeSaveAssessmentForLaterInPersonalRepository").execute(assessment, user)    

                    #handle the 'onBeforeSaveAssessmentForLaterInRepository' event for the repositories in which the assessment is located
                    for repositoryIdentity in repositoryIdentitiesInWhichAssessmentIsLocated:
                        #(we don't include the user repository)
                        if repositoryIdentity != user.getIdentity():
                            repository = self.getComponent(repositoryIdentity)
                            if repository is not None and repository.isEventEnabled("onBeforeSaveAssessmentForLaterInRepository"):
                                repository.getEnabledEvent("onBeforeSaveAssessmentForLaterInRepository").execute(assessment, user)                        

                    #handle the 'onBeforeSaveAssessmentForLaterInAnyRepository' event for the model
                    if self.isEventEnabled("onBeforeSaveAssessmentForLaterInAnyRepository"):
                        self.getEnabledEvent("onBeforeSaveAssessmentForLaterInAnyRepository").execute(assessment, user)

                #save the assessment using the assessment storage service
                storageService.saveAssessment(assessment)

                #handle the 'onAfterSaveAssessmentInPersonalRepository' event for the user repository (if it's in the user repository)
                if isAssessmentInPersonalRepository and self.isEventEnabled("onAfterSaveAssessmentInPersonalRepository"):
                    self.getEnabledEvent("onAfterSaveAssessmentInPersonalRepository").execute(assessment, user)    

                #handle the 'onAfterSaveAssessmentInRepository' event for the repositories in which the assessment is located
                for repositoryIdentity in repositoryIdentitiesInWhichAssessmentIsLocated:
                    #(we don't include the user repository)
                    if repositoryIdentity != user.getIdentity():
                        repository = self.getComponent(repositoryIdentity)
                        if repository is not None and repository.isEventEnabled("onAfterSaveAssessmentInRepository"):
                            repository.getEnabledEvent("onAfterSaveAssessmentInRepository").execute(assessment, user)                        

                # Handle the 'onAfterSaveAssessmentInAnyRepository' event for the model
                if self.isEventEnabled("onAfterSaveAssessmentInAnyRepository"):
                    self.getEnabledEvent("onAfterSaveAssessmentInAnyRepository").execute(assessment, user)

                #now checkin the assessment
                storageService.checkinAssessment(assessment, user)

                #go to the appropriate destination
                return response.redirect(destinationURL)

        else:
            #process the GET request
            
            #NOTE: GET requests only occur when the assessment is
            #opened.  After that, POST requests handle inter-page
            #navigation.  That is, all other requests from the
            #assessmenteditor are POSTS back to this method.

            #set the assessmentIdentity in the session
            #(we could also store the assessment in the session, but we'll do this optimization later)
            
            session["assessmentIdentity"] = assessmentIdentity

            #set the page in the request to be the first visible page in the model
            pages = self.getPages(assessment)
            page = self.getFirstShownComponent(pages, assessment)            
            if page is None:
                page = pages[0]
            request["PAGE"] = page

            #add the assessment to the request (so we can pass it around when rendering the assessment)
            request["ASSESSMENT"] = assessment
            
            #
            # 20060711 johnm    Fixed a bug whereby the openedFrom repository was not being
            #                   carried through. Various submit actions suffered
            #
            request["ASSESSMENT_OPENEDFROM"] = assessment.getOpenedFromRepositoryIdentity()

            #handle the 'onBeforeVisiting' event for the page
            if page.isEventEnabled("onBeforeVisiting"):
                page.getEnabledEvent("onBeforeVisiting").execute(assessment, user)

            #initialise the request state variables (used to help render a page)
            request['ASSESSMENT_REVISION'] = assessment.getRevision()
            
            request["REFRESH_JAVASCRIPT"] = []
            request["DECLARATIONS_JAVASCRIPT"] = {}     #a dictionary (set) to prevent individual component types duplicating declarations
            request["ONLOAD_JAVASCRIPT"] = {}           #a dictionary (set) to prevent individual component types duplicating onload statements
            request["ISPREVIEW"] = false
            #request["ELEMENTS"] = page.getAcquiredElementsDictionary().values()
        
            #request["ELEMENTS_BY_TYPE"] = page.getAcquiredElementsByTypeDictionary().values()
            request["EXIT_URL"] = exitURL

            #now forward the request to the assessment editor to render the page for the assessment
            return self.Model_assessmenteditor(REQUEST=request, **args)

    #---------------------------------------------------------------#

    def dump(self, message):
        """
            Dumps the specified message to stdout
        """

        print "[KnowledgeEngine] DUMPED:\n%s" % message

    #---------------------------------------------------------------#

    def getAllAssessmentsFilter(self):
        """
            Returns an assessment filter that represents all assessments 
            in the Model
        """

        return AssessmentFilter("All Assessments", None, self, ['*'])

    #---------------------------------------------------------------#
    
    def getSearchRelation(self):
        """ 
            Returns this models search relation 
        """
        
        return getattr(self, 'searchRelation', '')
    
    #---------------------------------------------------------------#
    
    def setSearchRelation(self, value):
        """     
            Sets this models search relation
        """
        self.searchRelation = value
    
    #---------------------------------------------------------------#
    
    
    def getEnterOnCreate(self):
        """ Should assessments be started immediately on creation? 
            RETURN TYP -> Boolean
        """
            
        return getattr(self, 'enterOnCreate', False)
    
    #---------------------------------------------------------------#
    
    def setEnterOnCreate(self, value):
        """ Sets the value of enterOnCreate 
            param value: BOOL
        """
        
        value = value and True or False # Convert to boolean
        self.enterOnCreate = value
    
    #---------------------------------------------------------------#
    
    def getAssessmentTitleTemplate(self):
        """ 
            Returns this models search relation 
        """
        
        return getattr(self, 'assessmentTitleTemplate', '')
    
    #---------------------------------------------------------------#
    
    
    def setAssessmentTitleTemplate(self, value):
        """     
            Sets this models search relation
        """
        self.assessmentTitleTemplate = ''
        
    #---------------------------------------------------------------#
    
    def getAssessmentIdentityTemplate(self):
        """ 
            Returns this models search relation 
        """
        
        return getattr(self, 'assessmentIdentityTemplate', None)
    
    #---------------------------------------------------------------#
    
    def getAutoCreateIndexes(self):
        """ 
            Should we auto create indexes?
        """
        
        return getattr(self, 'autoCreateIndexes', False)
    
    #---------------------------------------------------------------#
    
    def isTaggable(self):
        """
            Are assessments for this model taggable?
        """
        
        return getattr(self, 'taggable', False)
    
    #---------------------------------------------------------------#
    
    def getIsTaggable(self):
        """
            Delegate method for isTaggable()
        """
        
        return self.isTaggable()
    
    #---------------------------------------------------------------#
    
    def setIsTaggable(self, value):
        """
            Sets the taggability of this Model's assessment
        """

        self.taggable = bool(value)   # Ensure BOOLEAN *sigh*
    
    #---------------------------------------------------------------#

    def toggleTaggable(self):
        """
            Toggle the taggable boolean status
        """
        
        self.setIsTaggable(not self.getIsTaggable())
    
    #---------------------------------------------------------------#

    def getTags(self):
        """
            Return all the tags for this model
        """
        
        storageService = self.knowledgeengine_assessmentstorageservice

        return storageService.getTagsForModel(self)

    #---------------------------------------------------------------#

    def removeTags(self, tags):
        """
            Remove specified tags from all assessments in this model
        """

        storageService = self.knowledgeengine_assessmentstorageservice

        storageService.removeTagsFromModel(self, tags)
        

    #---------------------------------------------------------------#

    def setAssessmentIdentityTemplate(self, value):
        """     
            Sets this models search relation
        """
        self.assessmentIdentityTemplate = ''        
        
    #---------------------------------------------------------------#
    
    def allowAssessmentNaming(self):
        """ Can assessments be named by the end user? """
        
        template = self.getAssessmentTitleTemplate()
        if template:
            return False
        else:
            return True
    
    #---------------------------------------------------------------#

    def isAccessibleByUser(self, user):
        """
            Returns boolean value depending on whether access to the
            model for the given user has been granted
        """
        
        permission = getSecurityService(self).getEffectivePermission('AccessModel', self.getIdentity(), user.getIdentity())
        
        print `permission`
        
        return permission is not None and permission.granted()

    #---------------------------------------------------------------#
    
    def hasUserAccessibleRepositories(self, user):
        """
            Returns true is the user has access to any repositories
            fro this model
        """

        # Check access to person repository
        accessPersonalRepositoryPermission = getSecurityService(self).getEffectivePermission('AccessPersonalRepository', self.getIdentity(), user.getIdentity())
        if accessPersonalRepositoryPermission is not None and accessPersonalRepositoryPermission.granted():
            return True

        # Check repositories
        for repository in self.getRepositories():
            if repository.isUserAccessible(user):
                return True
        
        return False
        
    #---------------------------------------------------------------#
    
    def getSearchIndexSummary( self ):
        """ Returns a summary of componentIdentity -> searchIndex mappings """
        
        summary = {}
        
        components = self.getDescendantsByInstanceOf( "Component" )
        for component in components:
            searchIndex = component.getSearchIndex()
            
            if searchIndex:
                summary[ component.getIdentity() ] = searchIndex
                
        return summary                
            
            
    #---------------------------------------------------------------#            
            
    def onAfterIdentityChange(self, identityMappings):
        """
            Handle Knowledge Engine specific actions after the
            identity of this object has changed.

            The identityMappings dictionary holds
            mappings from oldIdentities -> newIdentities
            (including this object)

        """

        #perform default identity changes
        GenericObject.onAfterIdentityChange(self, identityMappings)

        self.assessmentTitleTemplate = updateKERF( self.assessmentTitleTemplate, identityMappings ) 
        

    #---------------------------------------------------------------#
    
    def getActionMode(self):
        """ Should this model allow actions to be executed one at a time or in batch mode? """
        
        return getattr(self, "actionMode", "Batch")
    
    
    #---------------------------------------------------------------#
    
    def getNoun(self):
        """ Return the Noun to use for this model """
        
        return getattr(self, 'noun', 'Assessment')
        
    #---------------------------------------------------------------#
    
    def getNoun_plural(self):
        """ Returns the plural form of the noun to use for this model """
        
        return getattr(self, 'noun_plural', 'Assessments')
    
    #---------------------------------------------------------------#

    def toXML(self):
        """
            Export the model, components and properties to an XML
            form.

            RETURNS: String
            
            TODO: Complete
        """

        pass

    #---------------------------------------------------------------#
    
    

#####################################################################
## CMF Required Class Initialisation

def addModel(self, id, REQUEST=None):
    """
        The Zope/CMF factory method to create an instance of a Knowledge Engine Model
    """

    #create a KnowledgeEngine identity for the new model
    identity = generateIdentity(Model.identity_type)

    #create an instance of the Model
    model = Model(id, identity)

    #add the object to the specified container (called self)
    self._setObject(id, model)

    if REQUEST is not None:
        REQUEST['RESPONSE'].redirect(model.absolute_url() + '/manage_main' )


#the CMF/Plone factory type information - used to create objects!)
factoryTypeInformation = {
                               'id'             : 'Model'
                             , 'meta_type'      : 'Model'
                             , 'description'    : "A KnowledgeEngine Model"
                             , 'icon'           : 'Model_icon.gif'
                             , 'product'        : 'KnowledgeEngine'
                             , 'factory'        : 'addModel'
                             , 'filter_content_types' : 0
                             , 'global_allow'   : 0
                             , 'immediate_view' : 'view'
                             , 'actions'        :
                                ( { 'id'            : 'view'
                                  , 'name'          : 'View'
                                  , 'action'        : 'string:${folder_url}/view'
                                  , 'permissions'   :
                                     (permissions.View,)
                                  , 'category'      : 'folder'
                                  }
                                ,
                                )
                             }

#####################################################################
## Class Initialisation

# Register Class Information with the KnowledgeEngine
registerPortalObjectClass(Model, addModel, factoryTypeInformation)

#####################################################################
